/**
 * 实现 websocket
 * 返回信息格式 {code:Number, msg:String, data:String}
 * code: 200 聊天消息  code:201 切歌
 */

const webSocket = require('ws');
const Utils = require('../utils');
const protobuf = require("protobufjs");
const path = require('path');

class WebSocket {

  constructor(options = {}) {
    this.options = {
      noServer: true,
      maxPayLoad: WebSocket.maxPayLoad,
      clientTracking: false, // 储存所有链接集合
      ...options
    };
    this.ws = new webSocket.Server(this.options, () => {
      const addr = this.ws.address();
      Utils.Logger.info(`WebSocket started on ${addr.address}:${addr.port}`);
    });
    this.connection();
    this.initProtobuf();
  }

  connection() {
    this.ws.on('connection', (socket) => {
      WebSocket.sockets.set(socket.ID, socket);
      socket.onerror = this.error;
      socket.onmessage = this.onmessage.bind(this);
      socket.onclose = this.onclose.bind(this);
      this.send(socket, {
        code: WebSocket.CODE.hello,
        msg: 'hello'
      });
    });
  }

  onclose(msg) {
    this.remove(msg.target);
  }

  onmessage(msg) {
    const data = this.deserialize(msg.data);
    if (!data) return;
    if (data.code === WebSocket.CODE.pong) {
      this.pong(msg.target);
    } else if (data.code === WebSocket.CODE.chat) {
      this.radio({
        code: WebSocket.CODE.chat,
        data: JSON.stringify({
          msg: data.msg,
          from: Utils.Func.encodeIp(msg.target.ID),
        })
      });
    }
  }

  initPING() {
    const pingData = this.serialize({
      code: WebSocket.CODE.ping
    });
    this.timer = setInterval(() => {
      WebSocket.sockets.forEach(socket => {
        this.ping(socket, pingData);
      });
    }, WebSocket.interval * 1000);
  }

  ping(socket, data) {
    let count = socket._count || 0;
    if (count >= 3) {
      this.remove(socket);
      return;
    }
    socket.send(data);
    socket._count = count += 1;
  }

  pong(socket) {
    let count = socket._count || 0;
    socket._count = count ? count -= 1 : 0;
    return;
  }

  // 使用 protobuf 
  send(socket, data) {
    if (socket.readyState === webSocket.OPEN) {
      const _d = this.serialize(data);
      socket.send(_d);
    }
  }

  once(...options) {
    this.send(...options);
    options[0] && options[0].close && options[0].close();
  }

  error(err) {
    Utils.Logger.error(err.stack);
  }

  radio(data, sockets = WebSocket.sockets) {
    const _d = this.serialize(data);
    sockets.forEach(socket => socket.send(_d));
  }

  async changeSong() {
    await this.initProtobuf();
    this.radio({
      code: WebSocket.CODE.systemChange,
    });
  }

  initProtobuf() {
    if (this.root) return;
    return new Promise(r => {
      protobuf.load(
        path.resolve(__dirname, '../protobuf/ws.proto'),
        (err, Root) => {
          if (err)
            return this.error(err);
          this.root = Root.lookupType('ws.Data');
          this.initPING();
          r();
        }
      )
    });
  }

  serialize(data) {
    return this.root.encode(this.root.create(data)).finish();
  }

  deserialize(data) {
    return this.root.toObject(this.root.decode(data));
  }

  get len() {
    return WebSocket.sockets.size;
  }

  userChangeSong(options) {
    this.radio({
      code: WebSocket.CODE.changeSong,
      data: JSON.stringify({
        from: Utils.Func.encodeIp(options.from),
      })
    });
  }

  userAddSong(options) {
    this.radio({
      code: WebSocket.CODE.putSong,
      data: JSON.stringify({
        from: Utils.Func.encodeIp(options.from),
        msg: options.name
      })
    });
  }

  remove(socket) {
    WebSocket.sockets.delete(socket.ID);
  }

}

WebSocket.maxPayLoad = 1024;
WebSocket.interval = 30; // 单位:s
WebSocket.CODE = {
  pong: 110, // pong针
  ping: 112, // ping针
  systemChange: 119, // 系统切歌
  hello: 100, // 问候
  chat: 200, // 聊天消息
  changeSong: 201, //切歌
  putSong: 202 //点歌
};
WebSocket.sockets = new Map();


module.exports = new WebSocket();